﻿using System;
using System.Collections.Generic;
using Framework;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.EnhancedTouch;
#if !UNITY_EDITOR
using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch;
#endif
using TouchPhase = UnityEngine.InputSystem.TouchPhase;

namespace Game
{
    public class TouchSystem : MonoSingleton<TouchSystem>
    {
        private List<TouchComponent> _touchComponentList = new List<TouchComponent>();
        private ObjectPool<GameTouch> _touchPool = new ObjectPool<GameTouch>();
        private GameTouch _emittingTouch = null;
        private List<GameTouch> _toEmitTouchList = new List<GameTouch>();
        
#if UNITY_EDITOR
        private GameTouch _preMouseLeftTouch = new GameTouch();
#endif

        public override void Awake()
        {
            base.Awake();
            EnhancedTouchSupport.Enable();
        }

        public void OnDestroy()
        {
            EnhancedTouchSupport.Disable();
        }

        public void AddTouchComponent(TouchComponent touchComponent)
        {
            if (!_touchComponentList.Contains(touchComponent))
            {
                _touchComponentList.Add(touchComponent);
                touchComponent.Retain();
            }
            if (_emittingTouch != null && touchComponent.IsUnused())
            {
                touchComponent.Retain();
            }
        }

        public void RemoveTouchComponent(TouchComponent touchComponent)
        {
            if (_emittingTouch == null)
            {
                if (_touchComponentList.Contains(touchComponent))
                {
                    _touchComponentList.Remove(touchComponent);
                }
            }
            else
            {
                if (_touchComponentList.Contains(touchComponent) && !touchComponent.IsUnused())
                {
                    touchComponent.Release();
                }
            }
        }

        private void Update()
        {
            if (_touchComponentList.Count == 0)
            {
                return;
            }
#if UNITY_EDITOR
            float mouseX = Mouse.current.position.x.ReadValue();
            float mouseY = Mouse.current.position.y.ReadValue();
            if (Mouse.current.leftButton.wasPressedThisFrame)
            {
                if (mouseX < 0f || mouseX > Screen.width || mouseY < 0f || mouseY > Screen.height)
                {
                    return;
                }
                _preMouseLeftTouch.Retain(MouseTouchIds.Left, TouchPhase.Began, 
                    new object[] {mouseX, mouseY});
                TryEmit(MouseTouchIds.Left, TouchPhase.Began, 
                    Mouse.current.position.x.ReadValue(), Mouse.current.position.y.ReadValue());
            }
            else if (Mouse.current.leftButton.isPressed)
            {
                float deltaX = mouseX - _preMouseLeftTouch.screenPosition.x;
                float deltaY = mouseY - _preMouseLeftTouch.screenPosition.y;
                if (Math.Abs(deltaX) <= 0.01f && Math.Abs(deltaY) <= 0.01f)
                {
                    _preMouseLeftTouch.Retain(MouseTouchIds.Left, TouchPhase.Stationary, 
                        new object[] {mouseX, mouseY});
                    TryEmit(MouseTouchIds.Left, TouchPhase.Stationary, 
                        new object[] {mouseX, mouseY});
                }
                else
                {
                    _preMouseLeftTouch.Retain(MouseTouchIds.Left, TouchPhase.Moved, 
                        new object[]
                        {
                            mouseX, mouseY,
                            deltaX, deltaY
                        });
                    TryEmit(MouseTouchIds.Left, TouchPhase.Moved, 
                        mouseX, mouseY,
                        deltaX, deltaY); 
                }
            }
            else if (Mouse.current.leftButton.wasReleasedThisFrame)
            {
                _preMouseLeftTouch.Retain(MouseTouchIds.Left, TouchPhase.Ended, 
                    new object[] {Mouse.current.position.x.ReadValue(), Mouse.current.position.y.ReadValue()});
                TryEmit(MouseTouchIds.Left, TouchPhase.Ended, 
                    Mouse.current.position.x.ReadValue(), Mouse.current.position.y.ReadValue()); 
            }
#else
            int activeTouchCount = Touch.activeTouches.Count;
            if (activeTouchCount > 0)
            {
                for (int i = 0; i < activeTouchCount; ++i)
                {
                    Touch touch = Touch.activeTouches[i];
                    TouchPhase touchPhase = touch.phase;
                    if (touchPhase == TouchPhase.Canceled)
                    {
                        touchPhase = TouchPhase.Ended;
                    }
                    switch (touchPhase)
                    {
                        case TouchPhase.Began:
                        case TouchPhase.Ended:
                        case TouchPhase.Stationary:
                            TryEmit(touch.touchId, touchPhase, 
                                new object[] {touch.screenPosition.x, touch.screenPosition.y});
                            break;
                        case TouchPhase.Moved:
                            TryEmit(touch.touchId, touchPhase, 
                                new object[]
                                {
                                    touch.screenPosition.x, touch.screenPosition.y,
                                    touch.delta.x, touch.delta.y
                                });
                            break;
                    }
                }
            }
#endif
        }

        private void TryEmit(int touchId, TouchPhase phase, params object[] args)
        {
            GameTouch touch = _touchPool.Retain(touchId, phase, args);
            if (_emittingTouch != null)
            {
                _toEmitTouchList.Add(touch);
                return;
            }
            Emit(touch);
        }
        
        private void Emit(GameTouch touch)
        {
            _emittingTouch = touch;
            int count = _touchComponentList.Count;
            for (int i = 0; i < count; ++i)
            {
                TouchComponent touchComponent = _touchComponentList[i];
                if (!touchComponent.IsUnused())
                {
                    touchComponent.OnTouch(_emittingTouch);
                }
            }

            count = _touchComponentList.Count;
            for (int i = count - 1; i >= 0; --i)
            {
                TouchComponent touchComponent = _touchComponentList[i];
                if (touchComponent.IsUnused())
                {
                    _touchComponentList.RemoveAt(i);
                }
            }
            
            _touchPool.Release(_emittingTouch);
            _emittingTouch = null;
            
            if (_toEmitTouchList.Count > 0)
            {
                GameTouch nextTouch = _toEmitTouchList[0];
                _toEmitTouchList.RemoveAt(0);
                Emit(nextTouch);
            }
        }
        
#if UNITY_EDITOR
        public static class MouseTouchIds
        {
            public const int Left = 1;
            public const int Right = 2;
            public const int Middle = 3;
        }
#endif
    }
}